/*
  Losu Copyright Notice
  --------------------
    Losu is an open source programming language project under the MIT license
  that can be used for both academic and commercial purposes. There are no
  fees, no royalties, and no GNU-like "copyright" restrictions. Losu qualifies
  as open source software. However, Losu is not public property, and founder
  'chen-chaochen' retains its copyright.

    Losu has been registered with the National Copyright Administration of the
  People's Republic of China, and adopts the MIT license as the copyright
  licensing contract under which the right holder conditionally licenses its
  reproduction, distribution, and modification rights to an unspecified public.

    If you use Losu, please follow the public MIT agreement or choose to enter
  into a dedicated license agreement with us.

  The MIT LICENSE is as follows
  --------------------
  Copyright  2020 - now  chen-chaochen

    Permission is hereby granted, free of charge, to any person obtaining a
  copy of this software and associated documentation files (the “Software”), to
  deal in the Software without restriction, including without limitation the
  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  sell copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.
*/

/*
    This document is part of the Losu Community OSPP - 2024 subject results
  contributed by Dongqiao Huang @ NEUQ, following MIT LICENSE Contributed to
  the Losu Community.

  The MIT LICENSE is as follows
  --------------------
  Copyright 2024 Dongqiao Huang

    Permission is hereby granted, free of charge, to any person obtaining a
  copy of this software and associated documentation files (the “Software”), to
  deal in the Software without restriction, including without limitation the
  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  sell copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.
 */


#include "hParser.hh"
#include "hError.hh"

namespace LosuPreCompiler
{

namespace Parser
{

using namespace LosuPreCompiler;
using namespace Error;
using namespace Lexer;
using namespace CodeGen;

hParser::hParser (std::string source, std::string fname)
{
  this->fname = fname;
  this->expString = "";
  this->lex = new hLexer (source, this->fname);
  this->cgen = new hCodegen (this->fname);
}

hParser::~hParser ()
{
  delete this->lex;
  delete this->cgen;
}

void
hParser::CheckMatch (std::int32_t needed, std::int32_t pre, std::int32_t line)
{
  if (this->lex->getTkType () != needed)
    {
      std::string nstr = "", pstr = "";
      if (needed < hLexer::synTktype::TK_EOZ)
        nstr += needed;
      else
        nstr += this->lex->synTklist[needed - hLexer::synTktype::TK_EOZ]
                    .tkValue;
      if (pre < hLexer::synTktype::TK_EOZ)
        pstr += pre;
      else
        pstr += this->lex->synTklist[pre - hLexer::synTktype::TK_EOZ].tkValue;
      throw hError_t ("need [" + nstr + "] after '" + pstr + "'", line,
                      hError::errCode::eCode_Parser);
    }
}

void
hParser::ParseExp ()
{
  hLexer *const lex = this->lex;
  std::int32_t n0 = 0, n1 = 0, n2 = 0; // n0 = ( n1 = [ n2 {
  while (1)
    {
      switch (lex->getTkType ())
        {
        case '(':
          n0++;
          break;
        case ')':
          n0--;
          break;
        case '[':
          n1++;
          break;
        case ']':
          n1--;
          break;
        case '{':
          n2++;
          break;
        case '}':
          n2--;
          break;
        case hLexer::synTktype::TK_EOZ:
          {
            throw hError_t ("unexpected end of file", lex->getLine (),
                            hError::errCode::eCode_Parser);
            break;
          }
        default:
          break;
        }
      lex->nextToken ();
      if (0 == n0)
        break;
    }
  if (0 != n1 || 0 != n2)
    throw hError_t ("Unbalanced expressions", lex->getLine (),
                    hError::errCode::eCode_Parser);
}

CodeGen::cgOutput
hParser::doParse ()
{
  try
    {
      hLexer *const lex = this->lex;
      hCodegen *const cgen = this->cgen;
      while (1)
        {
          switch (lex->getTkType ())
            {
            case hLexer::synTktype::TK_IMPORT:
              {
                lex->nextToken ();
                // check name after import
                this->CheckMatch (hLexer::synTktype::TK_NAME,
                                  hLexer::synTktype::TK_IMPORT,
                                  lex->getLine ());
                break;
              }
            case hLexer::synTktype::TK_MODULE:
              {
                lex->nextToken ();
                std::int32_t l = lex->getLine ();
                // check name after module
                this->CheckMatch (hLexer::synTktype::TK_NAME,
                                  hLexer::synTktype::TK_MODULE, l);
                cgen->addModule (this->p_parModule (), l);
                break;
              }
            case hLexer::synTktype::TK_EOZ:
              {
                return cgen->genCode ();
              }
            default:
              {
                throw hError_t ("invalid experssion", lex->getLine (),
                                hError::errCode::eCode_Parser);
              }
            }
          lex->nextToken (); // next token
        }
    }
  catch (hError_t &err)
    {
      hError ("Syntax-Parser", this->fname, err.getLine (), err.getEcode (),
              err.getErr ());
    }
  return cgen->genCode ();
}

cgType_module
hParser::p_parModule ()
{
  hLexer *const lex = this->lex;
  hCodegen *const cgen = this->cgen;

  cgType_module modObj;
  // set  name;
  modObj.name = lex->getTkValue ();

  // until end
  while (1)
    {
      lex->nextToken ();
      switch (lex->getTkType ())
        {
        case hLexer::synTktype::TK_END: // return module, tk = END;
          {
            return modObj;
            break;
          }
        case hLexer::synTktype::TK_SUB:
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();
            // check name after sub
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_SUB, l);
            cgen->addSubmod (this->p_parSubmod (), &modObj.subMod, l);
            break;
          }
        default:
          {
            throw hError_t ("invalid experssion", lex->getLine (),
                            hError::errCode::eCode_Parser);
          }
        }
    }
}

cgType_submod
hParser::p_parSubmod ()
{
  hLexer *const lex = this->lex;
  hCodegen *const cgen = this->cgen;

  cgType_submod smodObj;
  // set  name;
  smodObj.name = lex->getTkValue ();

  // until end
  while (1)
    {
      lex->nextToken ();
      switch (lex->getTkType ())
        {
        case hLexer::synTktype::TK_END: // return module, tk = END;
          {
            return smodObj;
            break;
          }
        case hLexer::synTktype::TK_POINT: // variable, back tk = NAME;
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();

            // check name after point
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_POINT, l);

            break;
          }
        case hLexer::synTktype::TK_DEF: // function, back tk = END;
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();

            // check name after def
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_DEF, l);

            // gen func
            cgen->addFunc (this->p_parFunc (), &smodObj.subFunc, l);
            break;
          }
        case hLexer::synTktype::TK_NUMBER: // variable, back tk = STRING;
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();

            // check name after number
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_NUMBER, l);

            // gen number
            cgen->addNumber (this->p_parNumber (), &smodObj.subNum, l);
            break;
          }
        case hLexer::synTktype::TK_KSTR: // variable, back tk = STRING;
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();

            // check name after string
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_KSTR, l);

            // gen string
            cgen->addString (this->p_parString (), &smodObj.subStr, l);
            break;
          }
        case hLexer::synTktype::TK_SCRIPT: // variable, back tk = STRING;
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();

            // check name after script
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_SCRIPT, l);

            // gen script
            cgen->addScript (this->p_parScript (), &smodObj.subScript, l);
            break;
          }
        case hLexer::synTktype::TK_OBJECT: // variable, back tk = END;
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();

            // check name after object
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_OBJECT, l);

            // gen object
            cgen->addObject (this->p_parObject (), &smodObj.subObj, l);
            break;
          }
        case hLexer::synTktype::TK_CLASS: // variable, back tk = END;
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();

            // check name after class
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_CLASS, l);

            // gen class
            cgen->addClass (this->p_parClass (), &smodObj.subCls, l);
            break;
          }
        default:
          {
            throw hError_t ("invalid experssion", lex->getLine (),
                            hError::errCode::eCode_Parser);
          }
        }
    }
}

cgType_func // parse func, back-tk = END;
hParser::p_parFunc ()
{
  hLexer *const lex = this->lex;

  cgType_func funcObj;
  // set name
  funcObj.name = lex->getTkValue ();
  funcObj.comment = "/**\n";

  // until end
  while (1)
    {
      lex->nextToken ();
      switch (lex->getTkType ())
        {
        case hLexer::synTktype::TK_END: // end of func
          {
            funcObj.comment += "*/";
            return funcObj;
            break;
          }
        case '(': // parse params
          {
            this->ParseExp (); // back tk = ':'
            this->CheckMatch (':', hLexer::synTktype::TK_DEF, lex->getLine ());
            break;
          }
        case hLexer::synTktype::TK_STRING: // string is Token Body
          {
            funcObj.comment += "* " + lex->getTkValue () + "\n";
            break;
          }
        default:
          {
            throw hError_t ("invalid experssion", lex->getLine (),
                            hError::errCode::eCode_Parser);
          }
        }
    }
}

cgType_number // parse number, back-tk = STRING;
hParser::p_parNumber ()
{
  hLexer *const lex = this->lex;

  cgType_number numObj;
  // set name
  numObj.name = lex->getTkValue ();

  lex->nextToken ();
  this->CheckMatch (hLexer::synTktype::TK_STRING, hLexer::synTktype::TK_NUMBER,
                    lex->getLine ());
  numObj.value = lex->getTkValue ();
  return numObj;
}

cgType_string // parse string, back-tk = STRING;
hParser::p_parString ()
{
  hLexer *const lex = this->lex;

  cgType_string strObj;
  // set name
  strObj.name = lex->getTkValue ();

  lex->nextToken ();
  this->CheckMatch (hLexer::synTktype::TK_STRING, hLexer::synTktype::TK_STRING,
                    lex->getLine ());
  strObj.value = "\"" + lex->getTkValue () + "\"";
  return strObj;
}

cgType_script // parse script, back-tk = STRING;
hParser::p_parScript ()
{
  hLexer *const lex = this->lex;

  cgType_script scrObj;
  // set name
  scrObj.name = lex->getTkValue ();

  lex->nextToken ();
  this->CheckMatch (hLexer::synTktype::TK_STRING, hLexer::synTktype::TK_SCRIPT,
                    lex->getLine ());
  scrObj.file = lex->getTkValue ();
  return scrObj;
}

cgType_object // parse object, back-tk = END;
hParser::p_parObject ()
{
  hLexer *const lex = this->lex;
  hCodegen *const cgen = this->cgen;

  cgType_object objObj;
  // set name
  objObj.name = lex->getTkValue ();

  // until end
  while (1)
    {
      lex->nextToken ();
      switch (lex->getTkType ())
        {
        case hLexer::synTktype::TK_END: // end of object
          {
            return objObj;
            break;
          }
        case hLexer::synTktype::TK_POINT: // variable, back tk = NAME;
          {
            lex->nextToken ();

            // check name after point
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_POINT, lex->getLine ());

            break;
          }
        case hLexer::synTktype::TK_DEF: // variable, back tk = END;
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();

            // check name after def
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_DEF, l);

            // gen func
            cgen->addFunc (this->p_parFunc (), &objObj.subFunc, l);
            break;
          }
        case hLexer::synTktype::TK_NUMBER: // number, back tk = STRING;
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();

            // check name after number
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_NUMBER, l);

            // gen number
            cgen->addNumber (this->p_parNumber (), &objObj.subNum, l);
            break;
          }
        case hLexer::synTktype::TK_KSTR: // string, back tk = STRING;
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();

            // check name after number
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_KSTR, l);

            // gen number
            cgen->addString (this->p_parString (), &objObj.subStr, l);
            break;
          }
        case hLexer::synTktype::TK_OBJECT: // variable, back tk = END;
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();

            // check name after object
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_OBJECT, l);

            // gen object
            cgen->addObject (this->p_parObject (), &objObj.subObj, l);
            break;
          }
        case hLexer::synTktype::TK_CLASS: // variable, back tk = END;
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();

            // check name after class
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_CLASS, l);

            // gen class
            cgen->addClass (this->p_parClass (), &objObj.subCls, l);
            break;
          }
        default:
          {
            throw hError_t ("invalid experssion", lex->getLine (),
                            hError::errCode::eCode_Parser);
          }
        }
    }
}

cgType_class // parse class, back tk = END;
hParser::p_parClass ()
{
  hLexer *const lex = this->lex;
  hCodegen *const cgen = this->cgen;

  cgType_class clsObj;
  clsObj.name = lex->getTkValue ();

  cgType_func initFunc;
  initFunc.name = "__init__";
  initFunc.comment = "/**\n";

  // check parameters
  lex->nextToken ();
  this->CheckMatch ('(', hLexer::synTktype::TK_CLASS, lex->getLine ());
  this->ParseExp ();
  this->CheckMatch (':', hLexer::synTktype::TK_CLASS, lex->getLine ());

  // until END
  while (1)
    {
      lex->nextToken ();
      switch (lex->getTkType ())
        {
        case hLexer::synTktype::TK_END:
          {
            initFunc.comment += "*/";
            cgen->addFunc (initFunc, &clsObj.subFunc, lex->getLine ());
            return clsObj;
            break;
          }
        case hLexer::synTktype::TK_POINT: // variable, back tk = NAME;
          {
            lex->nextToken ();

            // check name after point
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_POINT, lex->getLine ());

            break;
          }
        case hLexer::synTktype::TK_DEF: // variable, back tk = END;
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();

            // check name after def
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_DEF, l);

            // gen func
            cgen->addFunc (this->p_parFunc (), &clsObj.subFunc, l);
            break;
          }
        case hLexer::synTktype::TK_NUMBER: // number, back tk = STRING;
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();

            // check name after number
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_NUMBER, l);

            // gen number
            cgen->addNumber (this->p_parNumber (), &clsObj.subNum, l);
            break;
          }
        case hLexer::synTktype::TK_KSTR: // key-string, back tk = STRING;
          {
            lex->nextToken ();
            std::int32_t l = lex->getLine ();

            // check name after number
            this->CheckMatch (hLexer::synTktype::TK_NAME,
                              hLexer::synTktype::TK_KSTR, l);

            // gen number
            cgen->addString (this->p_parString (), &clsObj.subStr, l);
            break;
          }
        case hLexer::synTktype::TK_STRING: // string, back tk = STRING;
          {
            initFunc.comment += "* " + lex->getTkValue () + '\n';
            break;
          }
        default:
          {
            throw hError_t ("invalid experssion", lex->getLine (),
                            hError::errCode::eCode_Parser);
          }
        }
    }
}

} // namespace Parser

} // namespace LosuPreCompiler


#ifdef HPARSERCC_MAIN

#include <fstream>
#include <iostream>
using namespace LosuPreCompiler;
using namespace Parser;

int
main ()
{
  std::ifstream fin ("test/demo.els", std::ios::in);
  std::string sSrc, tmp;
  while (std::getline (fin, tmp))
    sSrc += tmp + '\n';
  fin.close ();

  hParser p (sSrc, "demo.els");
  CodeGen::cgOutput out = p.doParse ();
  std::ofstream ofile ("test/demo.els.c");
  std::ofstream hfile ("test/demo.els.h");
  hfile << out.headFile.headcmt << std::endl
        << out.headFile.declare << std::endl;
  ofile << out.srcFile.headcmt << std::endl
        << out.srcFile.declare << std::endl
        << out.srcFile.module << std::endl
        << out.srcFile.submod << std::endl
        << out.srcFile.object << std::endl
        << out.srcFile.classs << std::endl
        << out.srcFile.func << std::endl;
  hfile.close ();
  ofile.close ();

  return 0;
}

#endif